﻿//#define ZDM_TEST
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

#if ZDM_TEST

#else

//Salty: 地图挖掘管理
//zdm: 寻路模块
public class MapManager : MonoBehaviour
{
    public static MapManager Instance
    {
        get
        {
            if (_Instance == null)
            {
                _Instance = FindObjectOfType<MapManager>();
            }
            if (!_Instance)
            {
                GameObject obj = new GameObject("MapManager");
                _Instance = obj.AddComponent<MapManager>();
            }
            return _Instance;
        }
        set
        {
            _Instance = value;
        }
    }
    private static MapManager _Instance;

    public GameObject clickableBoardPrefab;
    int width;
    int height;
    float xGap;
    float yGap;
    private Block[,] maps;

    Vector3 originPos = ConstOfMap.originPos;

    public int[,] sDist;
    public int[,] sDistIgnoreTower;
    public int[,] tDist;
    public int[,] tDistIgnoreTower;
    public int[,] pDist;
    public int[,] pDistIgnoreTower;

    public uint[,] map;
    public float[,] hp;

    public Vector2Int enterPos;
    public Vector2Int playerPos;
    public Vector2Int goalPos;

    private static readonly int[] mx = new int[4] { 1, 0, -1, 0 };
    private static readonly int[] my = new int[4] { 0, -1, 0, 1 };

    private static readonly int[] bx = new int[8] { 1, 1, 1, 0, -1, -1, -1, 0 };
    private static readonly int[] by = new int[8] { 1, 0, -1, -1, -1, 0, 1, 1 };

    private static uint DIGBIT = (1u << 31);
    private static uint TERBIT = (1u << 30);
    private static uint BLDBIT = (1u << 29);
    private static uint RUNBIT = (1u << 28);
    private static uint TYPMSK = 267386881;
    private static uint VALMSK = 1048575;
    private static uint ALLMSK = 4294967295;
    private static int VALLEN = 20;

    void Awake()
    {
        xGap = ConstOfMap.xGap;
        yGap = ConstOfMap.yGap;
        if (!ConstOfMap.loadgame)
        {
            enterPos = ConstOfMap.initEnterPos;
            playerPos = ConstOfMap.initPlayerPos;
            goalPos = ConstOfMap.initGoalPos;
            width = ConstOfMap.width;
            height = ConstOfMap.height;
            map = new uint[width, height];
            hp = new float[width, height];
        }
        else
        {
            LoadMap();
        }
        for (int i = 0; i < ConstOfMap.FLOORMAX; i++)
        {
            texture[i] = Resources.Load<Sprite>("Image/floor_4/floor_4_" + i.ToString());
        }
        sDist = new int[width, height];
        sDistIgnoreTower = new int[width, height];
        tDist = new int[width, height];
        tDistIgnoreTower = new int[width, height];
        pDist = new int[width, height];
        pDistIgnoreTower = new int[width, height];
        maps = new Block[width, height];
        Tag.goalIsLocked = false;
        if (!ConstOfMap.setseed)
            RandomGenerator.Instance.SetSeed((uint)(System.DateTime.Now.Ticks));
        else RandomGenerator.Instance.SetSeed(ConstOfMap.seed);
    }

    // Start is called before the first frame update
    void Start()
    {
        if (!ConstOfMap.loadgame)
            BuildMap();
        Init();

        GameObject entrance = Resources.Load("Prefabs/Entrance") as GameObject;
        Vector2 p2 = Function.LogicPosToWorldPos(ConstOfMap.initEnterPos);
        GameObject.Instantiate(entrance, new Vector3(p2.x, p2.y, p2.y * 0.1f), Quaternion.identity);

        UIManager.Instance.PushPanel("MainPausePanel");
        if(!ConstOfMap.loadgame)
        {
            EventManager.Instance.OnTriggerEvent(20000);
        }
    }

    bool gameover = false;

    // Update is called once per frame
    void Update()
    {
        if (goalPos == enterPos && !Tag.gameover && !gameover)
        {
            Tag.gameover = true;
            gameover = true;
            UIManager.Instance.Clear();
            UIManager.Instance.PushPanel("GameOverPanel");

        }
    }

    public Save CreateSave()
    {
        Save S = new Save();
        uint[] mmap = new uint[width * height];
        float[] mhp = new float[width * height];
        List<BuffList> bufflist = new List<BuffList>();
        List<BuffInfo> JOJObuff = Player.Instance.GetBuff();
        for (int i = 0; i < width; i++)
            for (int j = 0; j < height; j++)
            {
                if (maps[i, j] != null)
                {
                    mhp[i * height + j] = maps[i, j].GetHP();
                    if (maps[i, j].buildingType != BuildingType.None)
                    {
                        List<BuffInfo> buffinfo = maps[i, j].GetBuff();
                        bufflist.Add(new BuffList(buffinfo, buffinfo.Count));
                        SetValCode(i, j, bufflist.Count);
                    }
                }
                else mhp[i * height + j] = 0;
                mmap[i * height + j] = map[i, j];
            }
        S.map = mmap;
        S.hp = mhp;
        S.width = width;
        S.height = height;
        S.enterPos.x = enterPos.x;
        S.enterPos.y = enterPos.y;
        S.playerPos.x = Player.Instance.GetPlayerPos().x;
        S.playerPos.y = Player.Instance.GetPlayerPos().y;
        S.goalPos.x = Goal.Instance.GetGoalPos().x;
        S.goalPos.y = Goal.Instance.GetGoalPos().y;
        S.AP = APController.Instance.GetAP();
        S.money = ResourceManager.Instance.GetCurrResourceCount(ResourceType.Money);
        S.JOJOhp = Player.Instance.GetHP();
        S.JOJOMaxhp = Player.Instance.GetMaxHP();
        S.data = Genetic.Instance.GetSoldierData();
        S.template = Genetic.Instance.GetSoldierTemplate();
        S.soldierTag = Genetic.Instance.GetSoldierTag();
        S.bufflist = bufflist;
        S.wave = APController.Instance.wave;
        S.JOJObuff = JOJObuff;
        return S;
    }

    void BuildMap()
    {
        for (int i = 0; i < width; ++i)
        {
            for (int j = 0; j < height; ++j)
            {
                if (RandomGenerator.Instance.Next(ConstOfMap.rarerate) == 0)
                {
                    if (RandomGenerator.Instance.Next(ConstOfMap.lavarate) == 0)
                        SetBlockCode(i, j, TerrainType.Lava);
                    else if (RandomGenerator.Instance.Next(ConstOfMap.heatrate) == 0)
                        SetBlockCode(i, j, BuildingType.Heat);
                    else if (RandomGenerator.Instance.Next(ConstOfMap.muddyrate) == 0)
                        SetBlockCode(i, j, BuildingType.Muddy);
                    else
                        SetBlockCode(i, j, TerrainType.Unbreakable);
                }
                else
                {
                    int eventid = EventManager.Instance.getRandomEvent();
                    eventid <<= 2;
                    eventid |= ConstOfMap.hardlist[RandomGenerator.Instance.Next(18)] - 1;
                    SetBlockCode(i, j, TerrainType.Breakable, eventid);
                }
                hp[i, j] = 100f;
            }
        }

        for (int i = 0; i < width; i++)
        {
            SetBlockCode(i, 0, TerrainType.Unbreakable);
            SetBlockCode(i, height - 1, TerrainType.Unbreakable);
        }
        for (int j = 0; j < height; j++)
        {
            SetBlockCode(0, j, TerrainType.Unbreakable);
            SetBlockCode(width - 1, j, TerrainType.Unbreakable);
        }

        for (int i = enterPos.x; i <= goalPos.x; ++i)
        {
            SetEmptyCode(i, enterPos.y);
        }
        for (int j = enterPos.y; j <= goalPos.y; ++j)
        {
            SetEmptyCode(enterPos.x, j);
        }

      
    }

    void LoadMap()
    {
        Save sv = ConstOfMap.save;
        width = sv.width;
        height = sv.height;
        map = new uint[width, height];
        hp = new float[width, height];
        for (int i = 0; i < width; i++)
            for (int j = 0; j < height; j++)
            {
                map[i, j] = sv.map[i * height + j];
                hp[i, j] = sv.hp[i * height + j];
            }
        enterPos = new Vector2Int(sv.enterPos.x, sv.enterPos.y);
        playerPos = Function.WorldPosToLogicPos(new Vector2(sv.playerPos.x, sv.playerPos.y));
        goalPos = Function.WorldPosToLogicPos(new Vector2(sv.goalPos.x, sv.goalPos.y));
    }

    void Init()
    {
        for (int i = 0; i < width; i++)
            for (int j = 0; j < height; j++)
            {
                if ((map[i, j] & DIGBIT) != 0)
                {
                    SetBlock(i, j);
                    MaskManager.Instance.breakStone(new Vector2Int(i, j));
                }
            }
        //InstantiateMap();
        UpdateDist();
    }

    public void SetBlockCode(int x, int y, BuildingType bt, int flag = 0)
    {
        map[x, y] &= DIGBIT;
        map[x, y] |= BLDBIT;
        map[x, y] |= ((uint)bt << VALLEN);
        map[x, y] |= (uint)flag;
    }

    public void SetRuinCode(int x, int y)
    {
        if ((map[x, y] & BLDBIT) == 0)
            return;
        map[x, y] |= RUNBIT;
    }

    public void DelRuinCode(int x, int y)
    {
        map[x, y] &= (ALLMSK ^ RUNBIT);
    }

    public void SetBlockCode(int x, int y, TerrainType tt, int flag = 0)
    {
        map[x, y] &= DIGBIT;
        map[x, y] |= TERBIT;
        map[x, y] |= ((uint)tt << VALLEN);
        map[x, y] |= (uint)flag;
    }

    public void SetEmptyCode(int x, int y)
    {
        map[x, y] = DIGBIT;
    }

    public void SetDigCode(int x, int y)
    {
        map[x, y] |= DIGBIT;
    }

    public void SetValCode(int x, int y, int flag)
    {
        map[x, y] &= (ALLMSK ^ VALMSK);
        map[x, y] |= (uint)flag;
    }

    public void SetBlock(int x, int y)
    {
        if (maps[x, y] == null)
            CreateBlock(x, y);
        for (int i = -2; i < 3; i++)
        {
            for (int j = -2; j < 3; j++)
            {
                MaskManager.Instance.Black(x + i, y + j);
            }
        }
        for (int i = 0; i < 8; i++)
        {
            int dx = x + bx[i], dy = y + by[i];
            if (CreateBlock(dx, dy) && IsRoad(new Vector2Int(dx, dy)) && (map[dx, dy] & DIGBIT) == 0)
            {
                SetBlock(dx, dy);
            }
        }
    }

    public void SetPlayerPos(Vector2Int pos)
    {
        if (playerPos != pos)
        {
            playerPos = pos;
            MaskManager.Instance.breakStone(pos);
            SetDigCode(pos.x, pos.y);
            UpdatePlayerDist();
            //Debug.Log("player dist updated");
        }
    }

    public void SetGoalPos(Vector2Int pos)
    {
        if (goalPos != pos)
        {
            goalPos = pos;
            UpdateGoalDist();
        }
    }

    private Sprite[] texture = new Sprite[ConstOfMap.FLOORMAX];

    private bool CreateBlock(int x, int y)
    {
        if (Function.OutOfRange(x, y) || maps[x, y] != null)
            return false;
        Block b = Instantiate(clickableBoardPrefab, Vector3.zero, Quaternion.identity, transform).GetComponent<Block>();
        b.SetPos(x, y);
        b.GetComponent<SpriteRenderer>().sprite = texture[RandomGenerator.Instance.Next(ConstOfMap.FLOORMAX)];
        b.transform.rotation = Quaternion.Euler(0, 0, 90f * RandomGenerator.Instance.Next(4));
        uint code = map[x, y];
        if ((code & TERBIT) != 0)
        {
            TerrainType tt = (TerrainType)((code & TYPMSK) >> VALLEN);
            int v = (int)(code & VALMSK);
            b.Init(tt, v);
            b.SetHP(hp[x, y]);
        }
        else if ((code & BLDBIT) != 0)
        {
            //Debug.Log(x.ToString() + " " + y.ToString() + " " + hp[x, y].ToString());
            //Debug.Log(((BuildingType)((code & TYPMSK) >> VALLEN)).ToString() + " " + ((int)(code & VALMSK)).ToString());
            BuildingType bt = (BuildingType)((code & TYPMSK) >> VALLEN);
            int v = (int)(code & VALMSK);
            b.Init(bt, v);
            if (v != 0)
            {
                BuffList bufflist = ConstOfMap.save.bufflist[v - 1];
                List<BuffInfo> buffInfos = new List<BuffInfo>();
                for (int i = 0; i < bufflist.length; i++)
                    buffInfos.Add(bufflist.buff[i]);
                b.SetBuff(buffInfos);
            }
            b.SetHP(hp[x, y]);
            if ((code & RUNBIT) != 0)
            {
                b.Destroy();
            }

        }
        else b.Clear();
        maps[x, y] = b;
        map[x, y] = code;
        //Debug.Log(maps[x, y].GetType());
        return true;
    }

    public int PlayerDistance(Vector2Int pos)
    {
        return Function.LogicDistance(pos, playerPos);
    }

    public void PlaceTower(BuildingType type, int logicX, int logicY)
    {
        if (maps[logicX, logicY].type != BlockType.Empty)
            return;
        SetBlockCode(logicX, logicY, type);
        maps[logicX, logicY].Init(type);
    }

    //判断坐标是否为通路,穿透防御塔及商店
    public bool IsRoad(Vector2Int pos)
    {
        return !Function.OutOfRange(pos) && maps[pos.x, pos.y].type != BlockType.Hard;
    }

    //判断坐标是否放有防御塔
    public bool IsTower(Vector2Int pos)
    {
        return !Function.OutOfRange(pos) &&
            (maps[pos.x, pos.y].buildingType == BuildingType.BaseCrossbow ||
            maps[pos.x, pos.y].buildingType == BuildingType.BaseCannon ||
            maps[pos.x, pos.y].buildingType == BuildingType.Production ||
            maps[pos.x, pos.y].buildingType == BuildingType.DarkFrameCrossbow ||
            maps[pos.x, pos.y].buildingType == BuildingType.Nut ||
            maps[pos.x, pos.y].buildingType == BuildingType.WhiteAngelCrossbow ||
            maps[pos.x, pos.y].buildingType == BuildingType.FireAngelCannon ||
            maps[pos.x, pos.y].buildingType == BuildingType.SoulCannon
            );
    }

    //判断坐标是否为空方格
    public bool IsEmpty(Vector2Int pos)
    {
        return !Function.OutOfRange(pos) && maps[pos.x, pos.y].type == BlockType.Empty && pos != playerPos && pos != goalPos; 
    }

    //判断坐标是否可通过
    public bool IsPassable(Vector2Int pos)
    {
        return !Function.OutOfRange(pos) && IsRoad(pos) && !IsTower(pos);
    }

    //更新地图
    private void Bfs(Vector2Int begin, int[,] dist)
    {
        Queue<Vector2Int> q = new Queue<Vector2Int>();
        bool[,] check = new bool[width, height];
        for (int i = 0; i < width; i++)
            for (int j = 0; j < height; j++)
                check[i, j] = false;
        q.Enqueue(begin);
        dist[begin.x, begin.y] = 0;
        check[begin.x, begin.y] = true;
        while (q.Count != 0)
        {
            Vector2Int pos = q.Dequeue();
            for (int i = 0; i < 4; i++)
            {
                Vector2Int nextPos = new Vector2Int(pos.x + mx[i], pos.y + my[i]);
                if (IsRoad(nextPos) && !check[nextPos.x, nextPos.y])
                {
                    dist[nextPos.x, nextPos.y] = dist[pos.x, pos.y] + 1;
                    check[nextPos.x, nextPos.y] = true;
                    q.Enqueue(nextPos);
                }
            }
        }
    }

    private void SPFA(Vector2Int begin, int[,] dist)
    {
        Queue<Vector2Int> q = new Queue<Vector2Int>();
        bool[,] check = new bool[width, height];
        for (int i = 0; i < width; i++)
            for (int j = 0; j < height; j++)
                dist[i, j] = 100000000;
        q.Enqueue(begin);
        dist[begin.x, begin.y] = 0;
        check[begin.x, begin.y] = true;
        while (q.Count != 0)
        {
            Vector2Int pos = q.Dequeue();
            check[pos.x, pos.y] = false;
            for (int i = 0; i < 4; i++)
            {
                Vector2Int nextPos = new Vector2Int(pos.x + mx[i], pos.y + my[i]);
                if (IsRoad(nextPos) && dist[nextPos.x, nextPos.y] > dist[pos.x, pos.y] + (IsTower(nextPos) ? 10000 : 1))
                {
                    dist[nextPos.x, nextPos.y] = dist[pos.x, pos.y] + (IsTower(nextPos) ? 10000 : 1);
                    if(!check[nextPos.x, nextPos.y])
                    {
                        check[nextPos.x, nextPos.y] = true;
                        q.Enqueue(nextPos);
                    }
                }
            }
        }
    }

    public void UpdateDist()
    {
        Bfs(enterPos, sDist);
        Bfs(playerPos, pDist);
        Bfs(goalPos, tDist);
        SPFA(goalPos, tDistIgnoreTower);
        SPFA(enterPos, sDistIgnoreTower);
        SPFA(playerPos, pDistIgnoreTower);
        //Debug.Log("distance updated.");
    }
    public void UpdateStartDist()
    {
        Bfs(enterPos, sDist);
        SPFA(enterPos, sDistIgnoreTower);
    }
    public void UpdatePlayerDist()
    {
        Bfs(playerPos, pDist);
        SPFA(playerPos, pDistIgnoreTower);
    }
    public void UpdateGoalDist()
    {
        Bfs(goalPos, tDist);
        SPFA(goalPos, tDistIgnoreTower);
    }
    public void UpdateDistIgnoreTower()
    {
        SPFA(goalPos, tDistIgnoreTower);
        SPFA(enterPos, sDistIgnoreTower);
        SPFA(playerPos, pDistIgnoreTower);
    }

    public void CleanBuilding(GameObject building)
    {
        Vector2Int logicPos = Function.WorldPosToLogicPos(building.transform.localPosition.x, building.transform.localPosition.y);
        if (BuildingManager.Instance.HasBuilding(building))
        {
            EffectObjectManager.Instance.CreateEffectObject("Prefabs/Effect/Smoke_Destroy", Function.LogicPosToWorldPos(logicPos));
            maps[logicPos.x, logicPos.y].Destroy();
            UpdateDistIgnoreTower();
        }
        else
        {
            if (building.name == "Player")
                UIManager.Instance.PushPanel("GameOverPanel");
            else
            {
                //Debug.Log(building.name);
                BattleObject bo = building.GetComponent<BattleObject>();
                bo.soldierData.score = bo.GetScore();
                Genetic.Instance.addSoldier(bo.soldierData);
            }
            Destroy(building);
        }
    }
}
#endif